#include "PolygonMesh.h"
#include "../View/View.h"

#include <fstream>
#include <sstream>

using namespace std;
using namespace CICore;

/// <summary>
/// Constructor
/// </summary>
/// <param name="n"> The mesh name </param>
CIGeom::PolygonMesh::PolygonMesh (const char* n) : Geometry (n) {}

/// <summary>
/// Constructor
/// </summary>
/// <param name="n"> The mesh name </param>
CIGeom::PolygonMesh::PolygonMesh (const string& n) : Geometry (n) {}

/// <summary>
/// Destructor
/// </summary>
CIGeom::PolygonMesh::~PolygonMesh() {
}








///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary>
/// Method called to render the mesh
/// </summary>
/// <param name="selectionMode"> The selection mode active in the scene </param>
void CIGeom::PolygonMesh::Render (unsigned int selectionMode) {
    VertexIter vertexIt = vertices.begin();
    FaceIter faceIt = faces.begin();
    Face* f = 0;
    Vertex* v = 0;

    //What and how to draw, change according to the current selection modality
    switch (selectionMode) {
        case FACES_SELECTION:               //FACES_SELECTION
            glLineWidth (LINE_WIDTH);                             //Sets the line width

            faceIt = faces.begin();

            glColor3f (FACE_SELECTED_COLOR.r, FACE_SELECTED_COLOR.g, FACE_SELECTED_COLOR.b);    //TODO -> Materials!!!            

            //Drawing feedback for all selected faces
            while (faceIt != faces.end()) {     //For each mesh face
                f = *faceIt;

                if (f->visible) {
                    if (f->selected) {                                                    //TODO -> Materials!!!

                        //Drawing the selection feedback
                        glBegin (GL_LINE_LOOP);
                            glNormal3f(f->normal.x, f->normal.y, f->normal.z);          //Sets the face normal

                            for (unsigned short i = 0; i < f->vertices.size(); i++) 
                                glVertex3f( f->vertices[i]->x,                      //Draws the (ith+1) face vertex
                                            f->vertices[i]->y, 
                                            f->vertices[i]->z );                        
                        glEnd(); 
                    }
                }
                faceIt++;
            }     


            faceIt = faces.begin();
            
            glColor3f (FACE_DEFAULT_FLAT_COLOR.r, FACE_DEFAULT_FLAT_COLOR.g, FACE_DEFAULT_FLAT_COLOR.b);                                //TODO -> Materials!!!

            //Drawing all faces
            while (faceIt != faces.end()) {     //For each mesh face
                f = *faceIt;
                
                if (f->visible) {
                    if (!(f->selected)) {                        
                        glColor3f (FACE_UNSELECTED_COLOR.r, FACE_UNSELECTED_COLOR.g, FACE_UNSELECTED_COLOR.b); 

                        //Drawing the face feedback
                        glBegin (GL_LINE_LOOP);
                            glNormal3f(f->normal.x, f->normal.y, f->normal.z);          //Sets the face normal

                            for (unsigned short i = 0; i < f->vertices.size(); i++) 
                                glVertex3f( f->vertices[i]->x,                      //Draws the (ith+1) face vertex
                                            f->vertices[i]->y, 
                                            f->vertices[i]->z );                                           
                        glEnd();

                        glColor3f (FACE_DEFAULT_FLAT_COLOR.r, FACE_DEFAULT_FLAT_COLOR.g, FACE_DEFAULT_FLAT_COLOR.b);                                //TODO -> Materials!!!
                    }

                    glDisable (GL_COLOR_MATERIAL);

                    float ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
                    float diffuse[] = { 0.8f, 0.8f, 0.8f, 1.0f };
                    float specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
                    glMaterialfv (GL_FRONT, GL_AMBIENT, ambient);
                    glMaterialfv (GL_FRONT, GL_DIFFUSE, diffuse);
                    glMaterialfv (GL_FRONT, GL_SPECULAR, specular);

                    //Drawing the face
                    glBegin (GL_POLYGON);
                        //glNormal3f(f->normal.x, f->normal.y, f->normal.z);          //Sets the face normal

                        for (unsigned short i = 0; i < f->vertices.size(); i++) {
                            Vertex* v = f->vertices[i];
                            glNormal3f(v->normal.x, v->normal.y, v->normal.z);      //Sets the vertex normal
                            glVertex3f( v->x, v->y, v->z );                         //Draws the (ith+1) face vertex                    
                        }
                    glEnd(); 
                    
                    glEnable (GL_COLOR_MATERIAL);
                    specular[0] = 0.0f;
                    specular[1] = 0.0f;
                    specular[2] = 0.0f;
                    glMaterialfv (GL_FRONT, GL_SPECULAR, specular);
                }
                
                faceIt++;
            }                                 
            break;

        case VERTICES_SELECTION:            //VERTICES_SELECTION
            glPointSize (POINT_SIZE);                             //Sets the point size            
            glLineWidth (LINE_WIDTH);                             //Sets the line width

            //Drawing the vertices
            glBegin (GL_POINTS);
                while (vertexIt != vertices.end()) {        //For each vertex
                    v = *vertexIt;
      
                    if (v->visible) {
                        //Chosing the vertex color depending on whether the vertex is selected or not
                        if (v->selected)
                             glColor3f (VERTEX_SELECTED_COLOR.r, VERTEX_SELECTED_COLOR.g, VERTEX_SELECTED_COLOR.b);  
                        else
                            glColor3f (VERTEX_UNSELECTED_COLOR.r, VERTEX_UNSELECTED_COLOR.g, VERTEX_UNSELECTED_COLOR.b);   
                        
                        glNormal3f (v->normal.x, v->normal.y, v->normal.z);      //Sets the vertex normal                                                
                        glVertex3f( v->x, v->y, v->z );     //draws the vertex
                    }

                    vertexIt++;
                }
            glEnd();

            //Drawing all faces
            while (faceIt != faces.end()) {     //For each mesh face
                f = *faceIt;                    

                if (f->visible) {
                    glColor3f (VERTEX_UNSELECTED_COLOR.r, VERTEX_UNSELECTED_COLOR.g, VERTEX_UNSELECTED_COLOR.b); 

                    //Drawing the face feedback
                    glBegin (GL_LINE_LOOP);
                        glNormal3f(f->normal.x, f->normal.y, f->normal.z);          //Sets the face normal

                        for (unsigned short i = 0; i < f->vertices.size(); i++) 
                            glVertex3f( f->vertices[i]->x,                      //Draws the (ith+1) face vertex
                                        f->vertices[i]->y, 
                                        f->vertices[i]->z );                   
                        
                    glEnd();

                    glColor3f (FACE_DEFAULT_FLAT_COLOR.r, FACE_DEFAULT_FLAT_COLOR.g, FACE_DEFAULT_FLAT_COLOR.b);                                //TODO -> Materials!!!
                    
                    glDisable (GL_COLOR_MATERIAL);

                    float ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
                    float diffuse[] = { 0.8f, 0.8f, 0.8f, 1.0f };
                    float specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
                    glMaterialfv (GL_FRONT, GL_AMBIENT, ambient);
                    glMaterialfv (GL_FRONT, GL_DIFFUSE, diffuse);
                    glMaterialfv (GL_FRONT, GL_SPECULAR, specular);

                    //Drawing the face
                    glBegin (GL_POLYGON);
                        //glNormal3f(f->normal.x, f->normal.y, f->normal.z);          //Sets the face normal

                        for (unsigned short i = 0; i < f->vertices.size(); i++) {
                            Vertex* v = f->vertices[i];
                            glNormal3f (v->normal.x, v->normal.y, v->normal.z);      //Sets the vertex normal
                            glVertex3f (v->x, v->y, v->z);                           //Draws the (ith+1) face vertex                    
                        }
                    glEnd(); 
                    
                    glEnable (GL_COLOR_MATERIAL);
                    specular[0] = 0.0f;
                    specular[1] = 0.0f;
                    specular[2] = 0.0f;
                    glMaterialfv (GL_FRONT, GL_SPECULAR, specular);
                }
                faceIt++;
            }    
            break;

    }
    //TODO -> bounding box must be renderizable
    //boundingBox.Render();
}

/// <summary>
/// Method called to load the names that identify this geometry in the OpenGL names stack
/// </summary>
void CIGeom::PolygonMesh::LoadNames() {
    LoadFacesNames();
}

/// <summary>
/// Method called to get the number of names that identify this geometry 
/// </summary>
int CIGeom::PolygonMesh::GetNumberOfNames() {
    return faces.size();
}

/// <summary>
/// Method called to load the faces names in the OpenGL names stack
/// </summary>
void CIGeom::PolygonMesh::LoadFacesNames() {
    FaceIter it = faces.begin();
    Face* f = 0;
    unsigned int faceIndex = 0;  

    //Drawing all faces    
    while (it != faces.end()) {     //For each mesh face
        f = *it;

        if (f->visible) {
            glLoadName (faceIndex);              //Load a name in the OpenGL names stack for the current face 

            //Drawing the face
            glBegin (GL_POLYGON);            
                glNormal3f (f->normal.x, f->normal.y, f->normal.z);          //Sets the face normal

                for (unsigned short i = 0; i<f->vertices.size(); i++) 
                    glVertex3f( f->vertices[i]->x,         //Draws the (ith+1) face vertex
                                f->vertices[i]->y, 
                                f->vertices[i]->z );                                    
            glEnd();
        }

        faceIndex++;
        it++;
    }
}

/// <summary>
/// Method called to load the vertices names in the OpenGL names stack
/// </summary>
void CIGeom::PolygonMesh::LoadVerticesNames() {
    VertexIter it = vertices.begin();
    Vertex* v = 0;
    unsigned int vertexIndex = 0;
    
    //If backface culling is enabled, must load only the "front vertices" names
    if (glIsEnabled (GL_CULL_FACE)) {
        GLfloat m[16];                                  //Temporary array to store a matrix 4x4
        glGetFloatv(GL_MODELVIEW_MATRIX, m);            //Gets the current model view matrix into the temporary array m

        //Recovering the z-axis vector of the camera's coordinate system                
        Vector3D f (-m[2], -m[6], -m[10]);
        double dotProduct;


        //Drawing the selected vertices
        while (it != vertices.end()) {              //For each vertex
            v = *it;

            //the dot product between the view direction and the vertex normal
            dotProduct = f.x * v->normal.x + f.y * v->normal.y + f.z * v->normal.z;

            if (v->visible && dotProduct < 0.0) {
                glLoadName (vertexIndex);               //Load a name in the OpenGL names stack for the current vertex 
                
                glNormal3f (v->normal.x, v->normal.y, v->normal.z);          //Sets the vertex normal

                glBegin (GL_POINTS);
                    glVertex3f (v->x, v->y, v->z );     //Draws the vertex
                glEnd();
            }

            vertexIndex++;
            it++;
        }
    }
    else {  //else, loads all names
        VertexIter it = vertices.begin();
        Vertex* v = 0;
        unsigned int vertexIndex = 0;

        //Drawing the selected vertices
        while (it != vertices.end()) {              //For each vertex
            v = *it;

            if (v->visible) {
                glLoadName (vertexIndex);               //Load a name in the OpenGL names stack for the current vertex 
                
                glNormal3f (v->normal.x, v->normal.y, v->normal.z);          //Sets the vertex normal

                glBegin (GL_POINTS);
                    glVertex3f( v->x, v->y, v->z );     //Draws the vertex
                glEnd();
            }

            vertexIndex++;
            it++;
        }
    }
}

/// <summary>
/// Method called to save a description of this geometry within an output stream
/// </summary>
/// <param name="device"> The file to save the geometry description </param>
void CIGeom::PolygonMesh::SaveAsCI3D (std::ofstream& out) {
    const char* tab = "    ";

    stringstream stream;

    stream << tab << tab << tab << "<geometry name=\"" << name.c_str() << "\">\n";

    stream << tab << tab << tab << tab << "<mesh>\n";

    stream << tab << tab << tab << tab << tab << "<vertices size=\"" << vertices.size() << "\">\n";

    map<Vertex*,int> index;
    Vertex* v;

    for (int i=0; i<vertices.size(); i++) {         //For each vertex
        v = vertices[i];

        stream << tab << tab << tab << tab << tab << tab << "<v x=\"" << v->x 
                                                         << "\" y=\"" << v->y 
                                                         << "\" z=\"" << v->z  
                                                         << "\"/>\n";
        index[v] = i;
    }

    stream << tab << tab << tab << tab << tab << "</vertices>\n";

    stream << tab << tab << tab << tab << tab << "<faces size=\"" << faces.size() << "\">\n";

    Face* f;

    for (int i=0; i<faces.size(); i++) {                                    //For each face
        f = faces[i];

        stream << tab << tab << tab << tab << tab << tab << "<f size=\"" << f->vertices.size() << "\">\n";
  
        stream << tab << tab << tab << tab << tab << tab << tab << "<v_indices>";

        for (int j=0; j<f->vertices.size(); j++)                        //For each vertex of the face, saves the index of the vertex
            stream << index[f->vertices[j]] << " ";
        
        stream << "</v_indices>\n";
        
        stream << tab << tab << tab << tab << tab << tab << "</f>\n"; 
    }

    stream << tab << tab << tab << tab << tab << "</faces>\n";

    stream << tab << tab << tab << tab << "</mesh>\n";

    stream << tab << tab << tab << "</geometry>\n";
    
    out << stream.rdbuf();
}
/***************************************************************************************************************
******************************************* endregion Utility Methods ******************************************
****************************************************************************************************************/
